// selectServer.hpp
#pragma once

#include <iostream>
#include <sys/select.h>
#include "TcpSocket.hpp"
using namespace std;

static const uint16_t defaultport = 8080;         // 缺省端口号
static const int fd_num_max = sizeof(fd_set) * 8; // fd_sex位图的比特位个数
int defaultfd = -1;                               // 缺省文件描述符值

class SelectServer
{
public:
    SelectServer(uint16_t port = defaultport)
        : _port(port)
    {
        for (int i = 0; i < fd_num_max; ++i)
        {
            rfd_array[i] = defaultfd;
            // cout << "rfd_array[" << i << "] = " << rfd_array[i] << endl;
        }
    }
    ~SelectServer()
    {
        _listensock.Close();
    }
    bool Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        return true;
    }
    void Start()
    {
        int listensock = _listensock.getfd();
        rfd_array[0] = listensock;

        for (;;)
        {
            fd_set rfds;
            FD_ZERO(&rfds);

            int maxfd = rfd_array[0];
            for (int i = 0; i < fd_num_max; ++i) // 第一次循环
            {
                if (rfd_array[i] == defaultfd)
                    continue;
                FD_SET(rfd_array[i], &rfds);
                if (maxfd < rfd_array[i])
                {
                    maxfd = rfd_array[i];
                    lg(Info, "max fd update, max fd is %d", maxfd);
                }
            }
            // accept本质就是检测并获取listensock上面的事件，新连接到来，等价于读事件就绪

            struct timeval timeout = {1, 0}; // timeout是个输入输出型参数，所以需要进行周期性的重复设置
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);

            switch (n)
            {
            case 0:
                cout << "time out, timeout: " << timeout.tv_sec << "." << timeout.tv_usec << endl;
                break;
            case -1:
                cerr << "select error" << endl;
                break;
            default:
                // 有事件就绪了，TODO
                Dispatcher(rfds); // 事件派发器
                break;
            }
        }
    }
    void Dispatcher(fd_set &rfds)
    {
        for (int i = 0; i < fd_num_max; ++i) // 第三个循环
        {
            int fd = rfd_array[i];
            if (fd == defaultfd)
                continue;

            // if (FD_ISSET(_listensock.getfd(), &rfds))
            if (FD_ISSET(fd, &rfds))
            {
                if (fd == _listensock.getfd())
                {
                    // 连接事件就绪了
                    Accepter(); // 连接管理器
                }
                else // non listensock
                {
                    // 读事件就绪
                    Recver(fd, i);
                }
            }
        }
    }
    void Accepter()
    {
        cout << "get a new link!!!" << endl;
        string clientip;
        uint16_t clientport;
        int sockfd = _listensock.Accept(&clientip, &clientport); // 会不会阻塞呢？不会
        if (sockfd < 0)
            return;
        lg(Info, "accept success, %s, %d, sockfd: %d", clientip.c_str(), clientport, sockfd);

        // 我们并不清楚，sockfd的读事件是否就绪，所以不能直接read，而是要把它添加进rfd_array里
        int pos = 1;
        for (; pos < fd_num_max; ++pos) // 第二个循环
        {
            if (rfd_array[pos] == defaultfd)
                break;
        }
        if (pos == fd_num_max)
        {
            lg(Warning, "server is full, close %d now", sockfd);
            close(sockfd);
        }
        else
        {
            rfd_array[pos] = sockfd;
            PrintFd(); // 测试
        }
    }
    void Recver(int fd, int pos)
    {
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug: 无法保证读上来的是一个完整的报文
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "get a message: " << buffer << endl;
        }
        else if (n == 0)
        {
            // 对方把文件描述符关了，我这边读到了结尾，所以要关闭fd
            lg(Info, "client quit, me too. close fd %d", fd);
            close(fd);
            rfd_array[pos] = defaultfd; // 并在读辅助数组中移除fd，本质是在select中移除fd
        }
        else
        {
            // 读出错
            lg(Warning, "recv error, close fd %d", fd);
            close(fd);
            rfd_array[pos] = defaultfd; // 并在读辅助数组中移除fd，本质是在select中移除fd
        }
    }
    void PrintFd()
    {
        cout << "online rfd list: ";
        for (int i = 0; i < fd_num_max; ++i)
        {
            if (rfd_array[i] != defaultfd)
            {
                cout << rfd_array[i] << " ";
            }
        }
        cout << endl;
    }

private:
    TcpSocket _listensock;
    uint16_t _port;
    int rfd_array[fd_num_max]; // 读辅助数组
    // int wfd_array[fd_num_max];  // 写辅助数组
};